import { CONFIG, getContext } from '../config.js';
import type { Provider, ProviderDailyRow } from '../shared/types.js';

const sleep = (ms: number) => new Promise(res => setTimeout(res, ms));

async function post(api_name: string, params: any, fields?: string[]): Promise<{ fields: string[]; items: any[] }> {
  const token = getContext().tushareToken || CONFIG.TUSHARE.API_TOKEN;
  if (!token) throw new Error('Tushare token not configured');
  const body: any = { api_name, token, params };
  if (fields && fields.length) body.fields = fields.join(',');
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), CONFIG.TUSHARE.TIMEOUT_MS);
  try {
    const resp = await fetch(CONFIG.TUSHARE.API_URL, {
      method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), signal: controller.signal
    });
    if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
    const json: any = await resp.json();
    if (json.code !== 0) throw new Error(json.msg || 'Tushare error');
    const data = json.data || {}; return { fields: data.fields || [], items: data.items || [] };
  } finally { clearTimeout(timeoutId); }
}

export const tushareProvider: Provider = {
  async fetchTickers() {
    const { fields, items } = await post('stock_basic', { list_status: 'L' }, ['ts_code', 'name']);
    const iCode = fields.indexOf('ts_code');
    const iName = fields.indexOf('name');
    return items.map((r: any[]) => ({ ts_code: String(r[iCode]), name: iName >= 0 ? String(r[iName]) : undefined }));
  },
  async fetchDaily(ts_code: string, start_date: string, end_date: string) {
    // backoff & rate limit
    let attempt = 0; const maxAttempt = 3;
    while (true) {
      try {
        const { fields, items } = await post('daily', { ts_code, start_date, end_date });
        return items.map((row: any[]) => {
          const obj: Record<string, any> = {};
          fields.forEach((f: string, i: number) => { obj[f] = row[i]; });
          const o: ProviderDailyRow = {
            trade_date: String(obj.trade_date),
            open: Number(obj.open),
            high: Number(obj.high),
            low: Number(obj.low),
            close: Number(obj.close),
            vol: obj.vol !== undefined ? Number(obj.vol) : undefined,
          };
          return o;
        });
      } catch (e) {
        if (++attempt >= maxAttempt) throw e;
        await sleep(1000 * attempt);
      } finally {
        await sleep(Math.ceil(60000 / CONFIG.TUSHARE.MAX_REQUESTS_PER_MIN));
      }
    }
  },
  async fetchAdjFactor(ts_code: string, start_date: string, end_date: string) {
    const { fields, items } = await post('adj_factor', { ts_code, start_date, end_date }, ['trade_date', 'adj_factor']);
    const iD = fields.indexOf('trade_date');
    const iF = fields.indexOf('adj_factor');
    const m = new Map<string, number>();
    for (const row of items) {
      const d = String(row[iD]);
      const f = Number(row[iF]);
      if (!isNaN(f)) m.set(d, f);
    }
    return m;
  }
};